package com.leigq.basaltools.util;


import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * OkHttp 工具类，构建者模式
 * <br/>
 * 参考：https://mp.weixin.qq.com/s/UjI17ixRF2LyDVxDhRkq-w
 * <br/>
 * 如何使用？ 参考 <code>com.blog.www.OkHttpToolsTest</code>
 *
 * @author leigq
 * @date 2021 -05-31 19:49:57
 */
@Slf4j
@SuppressWarnings(value = "unchecked")
public class OkHttpUtils<T> {
    /**
     * The okHttpClient.
     */
    private volatile OkHttpClient okHttpClient;

    /**
     * The Header map.
     */
    private final Map<String, String> headerMap = new LinkedHashMap<>(16);

    /**
     * The Param map.
     */
    private final Map<String, T> paramMap = new LinkedHashMap<>(16);

    /**
     * The Url.
     */
    private String url;

    /**
     * The Request.
     */
    private Request.Builder request;

    /**
     * 默认连接超时，单位：秒
     */
    private static final int DEF_CONNECT_TIMEOUT = 15;

    /**
     * 默认读超时，单位：秒
     */
    private static final int DEF_READ_TIMEOUT = 20;

    /**
     * 默认写超时，单位：秒
     */
    private static final int DEF_WRITE_TIMEOUT = 20;


    /**
     * 初始化 okHttpClient，单例，并且允许https访问
     */
    private OkHttpUtils(int connectTimeout, int writeTimeout, int readTimeout) {
        if (okHttpClient == null) {
            synchronized (OkHttpUtils.class) {
                if (okHttpClient == null) {
                    TrustManager[] trustManagers = buildTrustManagers();
                    okHttpClient = new OkHttpClient.Builder()
                            .connectTimeout(connectTimeout, TimeUnit.SECONDS)
                            .writeTimeout(writeTimeout, TimeUnit.SECONDS)
                            .readTimeout(readTimeout, TimeUnit.SECONDS)
                            .sslSocketFactory(createSslSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
                            .hostnameVerifier((hostName, session) -> true)
                            .retryOnConnectionFailure(true)
                            .build();
                    addHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
                }
            }
        }
    }


    /**
     * 创建 OkHttpUtils 实例，自定义属性
     * <br/>
     *
     * @param connectTimeout 连接超时，单位：秒
     * @param writeTimeout   读超时，单位：秒
     * @param readTimeout    写超时，单位：秒
     * @return ok http tools
     */
    public static <T> OkHttpUtils<T> builder(int connectTimeout, int writeTimeout, int readTimeout) {
        return new OkHttpUtils<>(connectTimeout, writeTimeout, readTimeout);
    }


    /**
     * 创建默认 OkHttpUtils， 连接超时：15 秒，写超时：20 秒，读超时：20 秒
     *
     * @return ok http tools
     */
    public static <T> OkHttpUtils<T> builder() {
        return builder(DEF_CONNECT_TIMEOUT, DEF_WRITE_TIMEOUT, DEF_READ_TIMEOUT);
    }


    /**
     * 添加 url
     *
     * @param url the url
     * @return ok http tools
     */
    public OkHttpUtils<T> url(String url) {
        this.url = url;
        return this;
    }

    /**
     * 添加参数
     *
     * @param key   参数名
     * @param value 参数值
     * @return ok http tools
     */
    public OkHttpUtils<T> addParam(String key, T value) {
        paramMap.put(key, value);
        return this;
    }

    /**
     * 添加参数
     *
     * @param params 参数实体，最终会以属性名作为参数名称，属性值作为参数值，支持 Map 集合及普通对象
     * @return ok http tools
     */
    public OkHttpUtils<T> addParam(T params) {
        if (Objects.isNull(params)) {
            return this;
        }

        if (params instanceof String) {
            throw new RuntimeException("not support String type");
        }

        final String json = JacksonUtils.toJson(params);
        if (!StringUtils.hasText(json)) {
            return this;
        }
        final HashMap<String, T> map = JacksonUtils.toObj(json, HashMap.class);
        if (CollectionUtils.isEmpty(map)) {
            return this;
        }
        map.forEach(paramMap::put);
        return this;
    }


    /**
     * 添加请求头
     *
     * @param key   参数名
     * @param value 参数值
     * @return ok http tools
     */
    public OkHttpUtils<T> addHeader(String key, String value) {
        headerMap.put(key, value);
        return this;
    }

    /**
     * 初始化 get 方法
     *
     * @return ok http tools
     */
    public OkHttpUtils<T> get() {
        request = new Request.Builder().get();
        final String urlParams = ParamUtils.toUrlParams(paramMap);
        if (!StringUtils.isEmpty(urlParams)) {
            url = url + urlParams;
        }
        request.url(url);
        return this;
    }

    /**
     * 初始化 post 方法，json 的方式提交数据
     *
     * @return ok http tools
     */
    public OkHttpUtils<T> post() {
        this.post(true);
        return this;
    }


    /**
     * 初始化 post 方法，表单方式提交
     *
     * @return ok http tools
     */
    public OkHttpUtils<T> postFormData() {
        this.post(false);
        return this;
    }


    /**
     * 同步请求
     *
     * @return string string
     */
    public String sync() throws IOException {
        this.setHeader(request);
        final Response response = okHttpClient.newCall(request.build()).execute();
        assert response.body() != null;
        return response.body().string();
    }

    /**
     * 异步请求，带有接口回调
     *
     * @param callBack the call back
     */
    public void async(ICallBack callBack) {
        this.setHeader(request);
        okHttpClient.newCall(request.build()).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callBack.onFailure(call, e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                assert response.body() != null;
                callBack.onSuccessful(call, response.body().string());
            }
        });
    }


    /**
     * 自定义一个接口回调
     */
    public interface ICallBack {
        /**
         * On successful.
         *
         * @param call the call
         * @param data the data
         */
        void onSuccessful(Call call, String data);

        /**
         * On failure.
         *
         * @param call     the call
         * @param errorMsg the error msg
         */
        void onFailure(Call call, String errorMsg);
    }


    /**
     * 生成安全套接字工厂，用于https请求的证书跳过
     *
     * @param trustAllCerts the trust all certs
     * @return ssl socket factory
     */
    private static SSLSocketFactory createSslSocketFactory(TrustManager[] trustAllCerts) {
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            return sc.getSocketFactory();
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            throw new RuntimeException("生成安全套接字工厂异常", e);
        }
    }

    /**
     * Build trust managers.
     *
     * @return the trust manager
     */
    private static TrustManager[] buildTrustManagers() {
        return new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
    }


    /**
     * 为request添加请求头
     *
     * @param request the request
     */
    private void setHeader(Request.Builder request) {
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            request.addHeader(entry.getKey(), entry.getValue());
        }
    }

    /**
     * 初始化post方法
     *
     * @param isJsonPost true等于json的方式提交数据，类似postman里post方法的raw; false等于普通的表单提交
     */
    private void post(boolean isJsonPost) {
        RequestBody requestBody;
        if (isJsonPost) {
            String json = JacksonUtils.toJson(paramMap);
            requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
        } else {
            FormBody.Builder formBody = new FormBody.Builder();
            paramMap.forEach((k, v) -> formBody.add(k, String.valueOf(v)));
            requestBody = formBody.build();
        }
        request = new Request.Builder().post(requestBody).url(url);
    }
}


